package org.duanyu.shopping_seckill_service.service;

import cn.hutool.bloomfilter.BitMapBloomFilter;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.dubbo.config.annotation.DubboService;
import org.duanyu.shopping_common.pojo.CartGoods;
import org.duanyu.shopping_common.pojo.Orders;
import org.duanyu.shopping_common.pojo.SeckillGoods;
import org.duanyu.shopping_common.result.BusException;
import org.duanyu.shopping_common.result.CodeEnum;
import org.duanyu.shopping_common.service.SeckillService;
import org.duanyu.shopping_seckill_service.mapper.SeckillGoodsMapper;
import org.duanyu.shopping_seckill_service.redis.RedissonLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@DubboService
@Service
public class SeckillServiceImpl implements SeckillService {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private BitMapBloomFilter bitMapBloomFilter;
    @Autowired
    private RedissonLock redissonLock;


    /**
     * 每分钟查询一次数据库，更新redis中的秒杀商品数据
     * 条件为startTime < 当时时间 < endTime,库存大于0
     */
    @Scheduled(cron = "0 * * * * *")
    public void refreshRedis() {
        System.out.println("同步mysql秒杀商品到redis....");
        //将redis中秒杀商品的库存数据同步到mysql中
        List<SeckillGoods> seckillGoodsListOld = redisTemplate.boundHashOps("seckillGoods").values();
        for (SeckillGoods seckillGoods : seckillGoodsListOld) {
            //在数据库查询秒杀商品
            QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("goodsId", seckillGoods.getGoodsId());
            SeckillGoods sqlSeckillGoods = seckillGoodsMapper.selectOne(queryWrapper);
            if (sqlSeckillGoods == null) {
                redisTemplate.boundHashOps("seckillGoods").delete(seckillGoods.getGoodsId());
            } else {
                //修改数据库中秒杀商品的库存,和redis中的库存保持一致
                sqlSeckillGoods.setStockCount(seckillGoods.getStockCount());
                seckillGoodsMapper.updateById(sqlSeckillGoods);
            }
        }

        //1.查询数据库中正在秒杀的商品
        QueryWrapper<SeckillGoods> wrapper = new QueryWrapper<>();
        Date date = new Date();
        String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
        wrapper.le("startTime", now)         //当前时间晚于开始时间
                .ge("endTime", now)      //当前时间早于结束时间
                .gt("stockCount", 0);    //库存大于0
        List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectList(wrapper);

        //2.删除之前的秒杀商品
        redisTemplate.delete("seckillGoods");

        //3.保存现在正在秒杀的商品
        for (SeckillGoods seckillGoods : seckillGoodsList) {
            redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(), seckillGoods);
            bitMapBloomFilter.add(seckillGoods.getGoodsId().toString());
        }

    }

    @SentinelResource("findPageByRedis")
    @Override
    public Page<SeckillGoods> findPageByRedis(int page, int size) {

        //1.查询所有秒杀商品列表
        List<SeckillGoods> seckillGoodsList = redisTemplate.boundHashOps("seckillGoods").values();

        //2.获取当前页商品列表
        //开启截取索引
        int start = (page - 1) * size;
        //结束截取索引
        int end = start + size > seckillGoodsList.size() ? seckillGoodsList.size() : start + size;
        //截取当前页的结果集
        List<SeckillGoods> seckillGoods = seckillGoodsList.subList(start, end);
        //3.构造页面对象
        Page<SeckillGoods> seckillGoodsPage = new Page<>();
        seckillGoodsPage.setCurrent(page)   //当前页
                .setSize(size)      //每页条数
                .setTotal(seckillGoodsList.size()) //总条数
                .setRecords(seckillGoods); //结果集
        return seckillGoodsPage;
    }

    @Override
    public SeckillGoods findSeckillGoodsByRedis(Long goodsId) {

        //先从布隆过滤器中判断商品是否存在，如果不存在，直接返回空,解决缓存穿透问题
        if (!bitMapBloomFilter.contains(goodsId.toString())) {
            System.out.println("布隆过滤器判断商品不存在");
            return null;
        }

        //1.从redis中查询秒杀商品
        SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.boundHashOps("seckillGoods").get(goodsId);
        //2.如果查询到商品，返回
        if (seckillGoods != null) {
            System.out.println("从redis中查询秒杀商品");
            return seckillGoods;
        }
        return null;
    }

    @Override
    public Orders createOrder(Orders orders) {

        //检查orders是否为null
        if (orders == null) {
            throw new IllegalArgumentException("订单对象不能为null");
        }

        //检查orders.getCartGoods()是否为null或为空
        if (orders.getCartGoods() == null || orders.getCartGoods().isEmpty()) {
            throw new IllegalArgumentException("订单中的商品列表不能为空");
        }

        String lockKey = orders.getCartGoods().get(0).getGoodId().toString();
        //检查redissionLock是否为空
        if (redissonLock == null) {
            throw new IllegalArgumentException("Redission锁对象未能正确初始化");
        }

        if (redissonLock.lock(lockKey, 10000)) {
            try {
                //1.生成订单对象
                orders.setId(IdWorker.getIdStr());      //手动生成订单id
                orders.setStatus(1); //设置订单状态为未支付
                orders.setCreateTime(new Date());    //生成订单对象时间
                orders.setExpire(new Date(new Date().getTime() + 1000 * 60 * 5));     //订单五分钟后过期

                //检查cartGoods.getPrince是否为null
                //计算商品价格
                CartGoods cartGoods = orders.getCartGoods().get(0);
                if (cartGoods == null) {
                    throw new IllegalArgumentException("订单中的商品下逆袭不能为空");
                }
                Integer num = cartGoods.getNum();
                BigDecimal price = cartGoods.getPrice();
                if (price == null) {
                    throw new IllegalArgumentException("商品价格不能为空");
                }
                BigDecimal sum = price.multiply(BigDecimal.valueOf(num));
                orders.setPayment(sum);

                //2.减少秒杀商品库存
                //查询秒杀商品
                SeckillGoods seckillGoods = findSeckillGoodsByRedis(cartGoods.getGoodId());

                //查询库存，库存不足抛出异常
                if (seckillGoods == null || seckillGoods.getStockCount() <= 0) {
                    throw new BusException(CodeEnum.NO_STOCK_ERROR);
                }

                //减少库存
                seckillGoods.setStockCount(seckillGoods.getStockCount() - seckillGoods.getNum());

                // 检查 redisTemplate 是否为 null
                if (redisTemplate == null) {
                    throw new IllegalStateException("RedisTemplate 未正确初始化");
                }
                redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(), seckillGoods);
                //3.将订单数据保存到redis中
                redisTemplate.setKeySerializer(new StringRedisSerializer());
                //设置订单1分钟过期
                redisTemplate.opsForValue().set(orders.getId(), orders, 100, TimeUnit.MINUTES);

                //给订单创建副本，副本的过期时间长于原订单,redis过期后触发过期时间时，此时只能拿到key，拿不到value
                //而过期事件需要回退商品库存，必须拿到value即订单详情，才能拿到商品数据，进行回退操作
                //我们保存一个订单副本，过期时间长于原订单，我们可以通过副本拿到订单数据
                redisTemplate.opsForValue().set(orders.getId() + "_copy", orders, 102, TimeUnit.MINUTES);
                System.out.println("下单成功,订单号:" + orders.getId());
                System.out.println("库存还有:" + seckillGoods.getStockCount());
                return orders;
            } finally {
                redissonLock.unLock(lockKey);
            }
        } else {
            return null;
        }
    }

    //根据id查询秒杀订单
    @Override
    public Orders findOrder(String id) {
        return (Orders) redisTemplate.boundHashOps("seckillGoods").get(id);
    }

    @Override
    public Orders pay(String orderId) {
        //1.查询订单，设置支付相关数据
        Orders orders = findOrder(orderId);
        if (orders == null) {
            throw new BusException(CodeEnum.ORDER_EXPIRED_ERROR);
        }

        orders.setStatus(2);
        orders.setPaymentTime(new Date());
        orders.setPaymentType(2);   //支付类型

        //2.从redis中删除订单数据
        redisTemplate.delete(orderId);

        //3.返回订单数据
        return orders;
    }

    @Override
    public void addRedisSeckillGoods(SeckillGoods seckillGoods) {
        redisTemplate.boundHashOps("seckillGoods").put(seckillGoods.getGoodsId(), seckillGoods);
        bitMapBloomFilter.add(seckillGoods.getGoodsId().toString());
    }

    @SentinelResource(value = "findSeckillGoodsByMySql", blockHandler = "mySqlBlockHandler")
    @Override
    public SeckillGoods findSeckillGoodsByMysql(Long goodsId) {
        //3.如果没有查到商品，
        QueryWrapper<SeckillGoods> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("goodId", goodsId);
        SeckillGoods seckillGoodsMysql = seckillGoodsMapper.selectOne(queryWrapper);
        System.out.println("从mysql中查询秒杀商品");
        //4.如果该商品不在秒杀状态，返回null
        Date now = new Date();
        if (seckillGoodsMysql == null
                || now.after(seckillGoodsMysql.getEndTime())
                || now.before(seckillGoodsMysql.getStartTime())
                || seckillGoodsMysql.getStockCount() <= 0) {
            return null;
        }
        //5.如果该商品在秒杀状态，将商品保存到redis中，并返回该商品
        addRedisSeckillGoods(seckillGoodsMysql);
        return seckillGoodsMysql;
    }

    /**
     * 降级处理
     *
     * @param goodsId 商品id
     * @param e
     * @return 返回空值
     */
    public SeckillGoods mySqlBlockHandler(Long goodsId, BlockException e) {
        System.out.println("服务降级方法");
        return null;
    }
}
